import {pushTarget,popTarget} from "./dep";
import {nextTick} from "../util";

/**
 * 1. 渲染组件会用到
 * 2. 计算属性会用到
 * 3. vm.watch会用到
 * */
let id = 0; // 每个watcher都有一个唯一的标识
class Watcher {
    /**
     *
     * @param vm 当前组件的实例 new Vue
     * @param exprOrFn 用户可能传入的是一个表达式 也有可能传入的是一个函数
     * @param cb 用户传入的回调函数 vm.$watch('msg',cb)
     * @param opts 一些其他参数
     */
    constructor(vm,exprOrFn,cb=()=>{},opts={}) {
        this.vm = vm;
        this.exprOrFn = exprOrFn;
        if (typeof exprOrFn === 'function') {
            this.getter = exprOrFn; // 让getter等于new Watcher传入的第二个参数
        }
        this.cb = cb;
        this.deps = [];
        this.depsId = new Set();
        this.opts = opts;
        this.id = id++;

        this.get(); // 让getter执行
    }

    // 默认创建watcher就会调用它↓
    // 给数据赋值导致视图更新会调它↓
    get()/*←取这名 杂想的???*/{
        pushTarget(this); // 当前的watcher,即 new Watcher时的那个实例。 数据变化了,需要让watcher重新执行

        // ↓这个getter即是new Watcher时传的第二个参数【updateComponent】(vue库的index.js中)
        this.getter/*←emmm 之所以叫getter...??不知道这个人咋想的,可能是因为暗示会在observer.js里Object.define的getter里做了手脚*/();

        popTarget();
    }

    addDep(dep) { // 同一个watcher 不应该重复记录dep
        let id = dep.id; //msg 的
        if(!this.depsId.has(id)) { // ←起到了去重的作用
            this.depsId.add(id);
            this.deps.push(dep); // 这样就让watcher 记住了当前的dep
            dep.addSub(this);
        }
    }

    // vm.x = y 赋值时会触发
    update(){
        // this.get()
        queueWatcher(this);
    }

    run(){
        this.get();
    }
}

let has = {};
let queue = [];
function flushQueue() {
    queue.forEach(watcher => watcher.run());
    has = {}; // 恢复正常 下一轮更新时继续使用
    queue = [];
}
/*
setTimeout(()=>{
  vm.msg = 'world';
  vm.msg = 'hello';
  vm.msg = 'world';
  vm.msg = 'xxx';
  vm.school.name = 'ahhh';
  // 我们期望虽然上面进行了多次 赋值 但只会执行一次渲染
}))
*/
function queueWatcher(watcher) { // 对重复的watcher进行过滤操作
    const id = watcher.id;
    if (has[id] == null) {
        has[id] = true;
        queue.push(watcher); // 相同的watcher只会存一个到queue中

        // 延迟清空队列
        // setTimeout(flushQueue, 0); // 异步方法 会等待所有同步方法执行完毕后待用此方法(即所有vm.x=y赋完值后,才会对模板重新进行编译)
        nextTick(flushQueue);
    }
}

export default Watcher;